Passed
Pull Request — master (#136)
by
unknown
03:10 queued 01:26
created

ID3Util.js ➔ decodeSize   A

Complexity

Conditions 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 8
c 0
b 0
f 0
rs 10
cc 1
1
const iconv = require('iconv-lite')
2
import * as ID3Definitions from './ID3Definitions'
3
4
const ENCODINGS = [
5
    'ISO-8859-1', 'UTF-16', 'UTF-16BE', 'UTF-8'
6
]
7
8
export class SplitBuffer {
9
    constructor(value = null, remainder = null) {
10
        this.value = value
11
        this.remainder = remainder
12
    }
13
}
14
15
export function splitNullTerminatedBuffer(buffer, encodingByte = 0x00) {
16
    const termination = { start: -1, size: 0 }
17
    if(encodingByte === 0x01 || encodingByte === 0x02) {
18
        termination.start = buffer.indexOf(Buffer.from([0x00, 0x00]))
19
        termination.size = 2
20
        if(termination.start !== -1 && buffer.length > (termination.start + termination.size)) {
21
            if(buffer[termination.start + termination.size] === 0x00) {
22
                termination.start += 1
23
            }
24
        }
25
    } else {
26
        termination.start = buffer.indexOf(0x00)
27
        termination.size = 1
28
    }
29
30
    if(termination.start === -1) {
31
        return new this.SplitBuffer(null, buffer.slice(0))
32
    }
33
    if(buffer.length <= termination.start + termination.length) {
34
        return new this.SplitBuffer(buffer.slice(0, termination.start), null)
35
    }
36
    return new this.SplitBuffer(buffer.slice(0, termination.start), buffer.slice(termination.start + termination.size))
37
}
38
39
export function terminationBuffer(encodingByte = 0x00) {
40
    if(encodingByte === 0x01 || encodingByte === 0x02) {
41
        return Buffer.alloc(2, 0x00)
42
    }
43
    return Buffer.alloc(1, 0x00)
44
}
45
46
export function encodingFromStringOrByte(encoding) {
47
    if(ENCODINGS.indexOf(encoding) !== -1) {
48
        return encoding
49
    } else if(encoding > -1 && encoding < ENCODINGS.length) {
50
        encoding = ENCODINGS[encoding]
51
    } else {
52
        encoding = ENCODINGS[0]
53
    }
54
    return encoding
55
}
56
57
export function stringToEncodedBuffer(str, encodingByte) {
58
    return iconv.encode(str, this.encodingFromStringOrByte(encodingByte))
59
}
60
61
export function bufferToDecodedString(buffer, encodingByte) {
62
    return iconv.decode(buffer, this.encodingFromStringOrByte(encodingByte)).replace(/\0/g, '')
63
}
64
65
export function getSpecOptions(frameIdentifier) {
66
    if(ID3Definitions.ID3_FRAME_OPTIONS[frameIdentifier]) {
67
        return ID3Definitions.ID3_FRAME_OPTIONS[frameIdentifier]
68
    }
69
70
    return {}
71
}
72
73
export function isValidID3Header(buffer) {
74
    if(buffer.length < 10) {
75
        return false
76
    }
77
    if(buffer.readUIntBE(0, 3) !== 0x494433) {
78
        return false
79
    }
80
    if([0x02, 0x03, 0x04].indexOf(buffer[3]) === -1 || buffer[4] !== 0x00) {
81
        return false
82
    }
83
    return this.isValidEncodedSize(buffer.slice(6, 10))
84
}
85
86
export function getFramePosition(buffer) {
87
    /* Search Buffer for valid ID3 frame */
88
    let framePosition = -1
89
    let frameHeaderValid = false
90
    do {
91
        framePosition = buffer.indexOf("ID3", framePosition + 1)
92
        if(framePosition !== -1) {
93
            /* It's possible that there is a "ID3" sequence without being an ID3 Frame,
94
             * so we need to check for validity of the next 10 bytes
95
             */
96
            frameHeaderValid = this.isValidID3Header(buffer.slice(framePosition, framePosition + 10))
97
        }
98
    } while (framePosition !== -1 && !frameHeaderValid)
99
100
    if(!frameHeaderValid) {
101
        return -1
102
    }
103
    return framePosition
104
}
105
106
/**
107
 * @param {Buffer} encodedSize
108
 * @return {boolean} Return if the header contains a valid encoded size
109
 */
110
 export function isValidEncodedSize(encodedSize) {
111
    // The size must not have the bit 7 set
112
    return ((
113
        encodedSize[0] |
114
        encodedSize[1] |
115
        encodedSize[2] |
116
        encodedSize[3]
117
    ) & 128) === 0
118
}
119
120
/**
121
 * ID3 header size uses only 7 bits of a byte, bit shift is needed
122
 * @param {number} size
123
 * @return {Buffer} Return a Buffer of 4 bytes with the encoded size
124
 */
125
 export function encodeSize(size) {
126
    const byte_3 = size & 0x7F
127
    const byte_2 = (size >> 7) & 0x7F
128
    const byte_1 = (size >> 14) & 0x7F
129
    const byte_0 = (size >> 21) & 0x7F
130
    return Buffer.from([byte_0, byte_1, byte_2, byte_3])
131
}
132
133
/**
134
 * Decode the size encoded in the ID3 header
135
 * @param {Buffer} encodedSize
136
 * @return {number} Return the decoded size
137
 */
138
 export function decodeSize(encodedSize) {
139
    return (
140
        (encodedSize[0] << 21) +
141
        (encodedSize[1] << 14) +
142
        (encodedSize[2] << 7) +
143
        encodedSize[3]
144
    )
145
}
146
147
export function getFrameSize(buffer, decode, ID3Version) {
148
    let decodeBytes
149
    if(ID3Version > 2) {
150
        decodeBytes = [buffer[4], buffer[5], buffer[6], buffer[7]]
151
    } else {
152
        decodeBytes = [buffer[3], buffer[4], buffer[5]]
153
    }
154
    if(decode) {
155
        return this.decodeSize(Buffer.from(decodeBytes))
156
    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
157
        return Buffer.from(decodeBytes).readUIntBE(0, decodeBytes.length)
158
    }
159
}
160
161
export function parseTagHeaderFlags(header) {
162
    if(!(header instanceof Buffer && header.length >= 10)) {
163
        return {}
164
    }
165
    const version = header[3]
166
    const flagsByte = header[5]
167
    if(version === 3) {
168
        return {
169
            unsynchronisation: !!(flagsByte & 128),
170
            extendedHeader: !!(flagsByte & 64),
171
            experimentalIndicator: !!(flagsByte & 32)
172
        }
173
    }
174 View Code Duplication
    if(version === 4) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
175
        return {
176
            unsynchronisation: !!(flagsByte & 128),
177
            extendedHeader: !!(flagsByte & 64),
178
            experimentalIndicator: !!(flagsByte & 32),
179
            footerPresent: !!(flagsByte & 16)
180
        }
181
    }
182
    return {}
183
}
184
185
export function parseFrameHeaderFlags(header, ID3Version) {
186
    if(!(header instanceof Buffer && header.length === 10)) {
187
        return {}
188
    }
189
    const flagsFirstByte = header[8]
190
    const flagsSecondByte = header[9]
191 View Code Duplication
    if(ID3Version === 3) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
192
        return {
193
            tagAlterPreservation: !!(flagsFirstByte & 128),
194
            fileAlterPreservation: !!(flagsFirstByte & 64),
195
            readOnly: !!(flagsFirstByte & 32),
196
            compression: !!(flagsSecondByte & 128),
197
            encryption: !!(flagsSecondByte & 64),
198
            groupingIdentity: !!(flagsSecondByte & 32),
199
            dataLengthIndicator: !!(flagsSecondByte & 128)
200
        }
201
    }
202
    if(ID3Version === 4) {
203
        return {
204
            tagAlterPreservation: !!(flagsFirstByte & 64),
205
            fileAlterPreservation: !!(flagsFirstByte & 32),
206
            readOnly: !!(flagsFirstByte & 16),
207
            groupingIdentity: !!(flagsSecondByte & 64),
208
            compression: !!(flagsSecondByte & 8),
209
            encryption: !!(flagsSecondByte & 4),
210
            unsynchronisation: !!(flagsSecondByte & 2),
211
            dataLengthIndicator: !!(flagsSecondByte & 1)
212
        }
213
    }
214
    return {}
215
}
216
217
export function processUnsynchronisedBuffer(buffer) {
218
    const newDataArr = []
219
    if(buffer.length > 0) {
220
        newDataArr.push(buffer[0])
221
    }
222
    for(let i = 1; i < buffer.length; i++) {
223
        if(buffer[i - 1] === 0xFF && buffer[i] === 0x00)
224
            continue
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
225
        newDataArr.push(buffer[i])
226
    }
227
    return Buffer.from(newDataArr)
228
}
229
230
export function getPictureMimeTypeFromBuffer(pictureBuffer) {
231
    if (pictureBuffer.length > 3 && pictureBuffer.compare(Buffer.from([0xff, 0xd8, 0xff]), 0, 3, 0, 3) === 0) {
232
        return "image/jpeg"
233
    } else if (pictureBuffer > 8 && pictureBuffer.compare(Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]), 0, 8, 0, 8) === 0) {
234
        return "image/png"
235
    } else {
236
        return null
237
    }
238
}
239